﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.WebControls;
using System.Web.UI.WebControls;
using System.Web.UI;
using Menu = Microsoft.SharePoint.WebControls.Menu;
using System.Reflection;

namespace TemplateLibraryConnector.ServerControls
{
    public class SPGridViewMultiSortFilter : SPGridView, IPostBackEventHandler
    {
        public SPGridViewMultiSortFilter() 
        {
            this.FilteredDataSourcePropertyName = "FilterExpression";
            this.FilteredDataSourcePropertyFormat = "{1} = '{0}'";
        }

        private char[] _sortingSeparator = { ',' }; 
        private string[] _filterSeparator = { "AND" };

        public string MultiValuesFields { get; set; }

        private UI.SPGridViewDataSource _gridDS;

        public UI.SPGridViewDataSource GridDataSource 
        { 
            get 
            { 
                return _gridDS; 
            } 
            private set 
            { 
                _gridDS = value; 
                this.DataSourceID = _gridDS.ID; 
            } 
        }

        internal string FilterExpression
        {
            get
            {
                if (ViewState["FilterExpression"] == null)
                { ViewState["FilterExpression"] = ""; }

                return (string)ViewState["FilterExpression"];
            }
            set
            {
                string thisFilterExpression = "(" + value.ToString() + ")";
                List<string> fullFilterExpression = new List<string>();
               
                if (ViewState["FilterExpression"] != null)
                {
                    string[] fullFilterExp = ViewState["FilterExpression"].ToString().Split(_filterSeparator, StringSplitOptions.RemoveEmptyEntries);
                    fullFilterExpression.AddRange(fullFilterExp);
                   
                    //if the filter is gone expression already exist?
                    int index = fullFilterExpression.FindIndex(s => s.Contains(thisFilterExpression));
                    if (index == -1)
                    { fullFilterExpression.Add(thisFilterExpression); }
                }
                else
                {
                    fullFilterExpression.Add(thisFilterExpression);
                }
                //loop through the list<T> and serialize to string
                string filterExp = string.Empty;
                fullFilterExpression.ForEach(s => filterExp += s + " AND ");
                filterExp = filterExp.Remove(filterExp.LastIndexOf(" AND "));
                if (!filterExp.EndsWith("))") && filterExp.Contains("AND"))
                { filterExp = "(" + filterExp + ")"; }
                ViewState["FilterExpression"] = filterExp; 
            }
        }


        public new string SortExpression
        {
            get
            {
                if (ViewState["SortExpression"] == null)
                { ViewState["SortExpression"] = ""; }

                return (string)ViewState["SortExpression"];
            }
            set
            {
                string[] thisSE = value.ToString().Split(' ');
                string thisSortExpression = thisSE[0];
                List<string> fullSortExpression = new List<string>();

                if (ViewState["SortExpression"] != null)
                {
                    string[] fullSortExp = ViewState["SortExpression"].ToString().Split(_sortingSeparator);
                    fullSortExpression.AddRange(fullSortExp);

                    //does the sort expression already exist?
                    int index = fullSortExpression.FindIndex(s => s.Contains(thisSortExpression));
                    if (index >= 0)
                    {
                        string s = string.Empty;
                        if (value.ToString().Contains("DESC"))
                        { s = value.ToString(); }
                        else
                        {
                            s = fullSortExpression[index];
                            if (s.Contains("ASC"))
                            { s = s.Replace("ASC", "DESC"); }
                            else
                            { s = s.Replace("DESC", "ASC"); }
                        }
                        //reset the sort direction
                        fullSortExpression[index] = s;
                    }
                    else
                    {
                        if (value.ToString().Contains("DESC"))
                        { fullSortExpression.Add(value.ToString()); }
                        else
                        { fullSortExpression.Add(thisSortExpression + " ASC"); }
                    }
                }
                else
                {
                    if (value.ToString().Contains("DESC"))
                    { fullSortExpression.Add(value.ToString()); }
                    else
                    { fullSortExpression.Add(thisSortExpression + " ASC"); }
                }
                //loop through the list<T> and serialize to string
                string sortExp = string.Empty;
                fullSortExpression.ForEach(s => sortExp += s);
                sortExp = sortExp.Replace(" ASC", " ASC,");
                sortExp = sortExp.Replace(" DESC", " DESC,");
                ViewState["SortExpression"] = sortExp.Remove(sortExp.LastIndexOf(',')); 
            }
        }


        protected override void OnLoad(EventArgs args)
        {
            if (GridDataSource == null)
                this.DataSourceID = null;
            base.OnLoad(args);

            ScriptLink.Register(this.Page, @"/_layouts/15/TemplateLibraryConnector/scripts/aspgridview.js", false);
        }

        protected override void LoadViewState(object savedState)
        {
            base.LoadViewState(savedState);

            if (Context.Request.Form["__EVENTARGUMENT"] != null &&
                 Context.Request.Form["__EVENTARGUMENT"].Contains("__ClearFilter__"))
            {
                //Removing this should allow you to clear just one filter expression
                //ViewState.Remove("FilterExpression");
            }
        }

        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            base.Attributes["callbackEventReference"] =
              base.Attributes["callbackEventReference"].Replace("SPGridView_FilterCallbackHandler",
                                                                 "ASPGridView_FilterCallbackHandler");

            MenuItemTemplate t = this.FindControl("SPGridViewFilterMenuTemplate").Controls[3] as MenuItemTemplate;
            t.ClientOnClickScript = t.ClientOnClickScript.Replace("__ClearFilter__", "__ClearFilter__;%FIELDNAME%");

            if (this.HeaderRow != null)
            {
                foreach (TableCell cell in this.HeaderRow.Cells)
                {
                    if (cell.Controls.Count > 0)
                    {
                        Menu menu = (cell.Controls[0] as Menu);
                        if (menu != null)
                        {
                            menu.ClientOnClickPreMenuOpen =
                              menu.ClientOnClickPreMenuOpen.Replace("SPGridView_FilterPreMenuOpen",
                                                                     "ASPGridView_FilterPreMenuOpen");
                        }
                    }
                }
            }

            base.Render(writer);
        }

        protected new void DoFilterPostBackEventProcessing( string filterData )
        {
            string filterExpression = String.Empty;
            if ( filterData.StartsWith( "__ClearFilter__" ) )
            {
            string[] data = filterData.Split( ';' );
            string fieldName = data[1];
 
            string oldFilterFieldName = base.Attributes["filterFieldName"];
            List<string> fieldNames = new List<string>( oldFilterFieldName.Split( ';' ) );
            fieldNames.Remove( fieldName );
            base.Attributes["filterFieldName"] = String.Join( ";", fieldNames.ToArray() );
 
            string oldFilterExpression = Convert.ToString( ViewState["FilterExpression"] );
            string[] filters = oldFilterExpression.Split( new string[] { " AND " }, StringSplitOptions.RemoveEmptyEntries );
            List<string> newFilters = new List<string>();
            foreach ( string filter in filters )
            {
                if ( !filter.Contains( String.Format( "[{0}]", fieldName ) ) )
                {
                newFilters.Add( filter );
                }
            }
            filterExpression = String.Join( " AND ", newFilters.ToArray() );
            }
            else
            {
            filterExpression = ExtractFilterExpression( filterData );
            }
 
            ViewState["FilterExpression"] = filterExpression;
            base.Attributes["filterExpression"] = filterExpression;
 
            if ( filterExpression == null ) { return; }
 
            DataSourceControl control = this.NamingContainer.FindControl( this.DataSourceID ) as DataSourceControl;
            PropertyInfo pi = control.GetType().GetProperty( "FilterExpression" );
            pi.SetValue( control, filterExpression, null );

            this.SelectedIndex = -1;
            base.RequiresDataBinding = true;
        }

        private string ExtractFilterExpression( string filterData )
        {
            string[] data = filterData.Split( ';' );
            string fieldName = data[0];
            if ( String.IsNullOrEmpty( fieldName ) )
            {
            return null;
            }
 
            string oldFilterFieldName = base.Attributes["filterFieldName"];
            if ( oldFilterFieldName != null )
            {
            if ( !oldFilterFieldName.Contains( fieldName ) )
            {
                base.Attributes["filterFieldName"] += ";" + fieldName;
            }
            }
            else
            {
            base.Attributes.Add( "filterFieldName", fieldName );
            }
 
            string fieldValue = data[1].Replace( "%3b", ";" ).Replace( "%25", "%" );
 
            string oldFilterExpression = Convert.ToString( ViewState["FilterExpression"] );
            string newFilterExpression = "";

            // if field is multi value we need to perform search using LIKE expression instead of "=" because
            // multi values are displayed as single string

            if (this.isMultipleValues(fieldName))
            {
                newFilterExpression = String.Format("[{0}] LIKE '%{1}%'", fieldName, fieldValue); 
            } 
            else 
            {   
                newFilterExpression = String.Format("[{0}] = '{1}'", fieldName, fieldValue); 
            }

            if ( !String.IsNullOrEmpty( oldFilterExpression ) )
            {
            if ( oldFilterExpression.Contains( newFilterExpression ) )
            {
                newFilterExpression = oldFilterExpression;
            }
            // also we need to check that current filed name already exists in the old filter expression
            // with another filed value. We need to replace it with new value. Otherwise it will be possible to filter 
            // within single column using several values. This is not OTB behavior. Sharepoint allows to select only one 
            // field value per column 
            else if (oldFilterExpression.Contains(string.Format("[{0}]", fieldName))) 
            { 
                string[] filters = oldFilterExpression.Split(new string[] { " AND " }, StringSplitOptions.RemoveEmptyEntries); 
                filters = filters.Where(f => !f.Contains(string.Format("[{0}]", fieldName))).ToArray(); 
                if (filters.Length != 0) 
                { 
                    newFilterExpression = String.Join(" AND ", new[] {String.Join(" AND ", filters), newFilterExpression}); 
                } 
            }
            else
            {
                newFilterExpression = String.Join( " AND ",
                                            new string[] { oldFilterExpression, newFilterExpression } );
            }
            }
 
            return newFilterExpression;
      }


        private bool isMultipleValues(string fieldName)
        { 
            try
            { 
                if (string.IsNullOrEmpty(this.MultiValuesFields)) 
                { 
                    return false; 
                } 
                var multiValuesFields = this.MultiValuesFields.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries); 
                if (multiValuesFields.Length == 0) 
                { 
                    return false; 
                } 
                return multiValuesFields.Any(t => t == fieldName); 
            } 
            catch 
            { 
                return false; 
            } 
        }


        private string GetValueAfterPrefix( string value, string prefix )
        {
          if ( String.IsNullOrEmpty( value ) ||
               String.IsNullOrEmpty( prefix ) ||
               !value.StartsWith( prefix ) )
          {
            return null;
          }
 
          if ( prefix.Length < value.Length )
          {
            return value.Substring( prefix.Length );
          }
 
          return String.Empty;
        }

        #region IPostBackEventHandler Members
 
            void IPostBackEventHandler.RaisePostBackEvent( string eventArgument )
            {
              string valueAfterPrefix = GetValueAfterPrefix( eventArgument, "__SPGridView__;" );
              if ( valueAfterPrefix != null )
              {
                string filterData = GetValueAfterPrefix( valueAfterPrefix, "__Sort__;" );
                if ( filterData == null )
                {
                  filterData = GetValueAfterPrefix( valueAfterPrefix, "__Filter__;" );
                  if ( filterData != null )
                  {
                    DoFilterPostBackEventProcessing( filterData );
                  }
                }
                else
                {
                  DoSortPostBackEventProcessing( filterData );
                }
              }
              else
              {
                base.RaisePostBackEvent( eventArgument );
              }
            }
 
    #endregion

        protected override void CreateChildControls() 
        { 
            base.CreateChildControls(); 
            this.Sorting += new GridViewSortEventHandler(GridView_Sorting); 
            this.RowDataBound += new GridViewRowEventHandler(GridView_RowDataBound); 
        }


        private void GridView_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            EnsureChildControls();

            if (sender == null || e.Row.RowType != DataControlRowType.Header)
            { 
                return; 
            }

            //make sure the header images are set
            BuildFilterView(_gridDS.FilterExpression);

            SPGridView grid = sender as SPGridView;

            // Show icon on filtered and sorted columns 
            for (int i = 0; i < grid.Columns.Count; i++)
            {
                DataControlField field = grid.Columns[i];

                if (field.SortExpression == "")
                    return;

                if (FilterExpression.Contains(field.SortExpression) && 
                    !string.IsNullOrEmpty(FilterExpression))
                {
                    PlaceHolder panel = HeaderImages(field, "//_layouts/15/images/filter.gif");
                    e.Row.Cells[i+1].Controls[0].Controls.Add(panel);
                }
                else if(SortExpression.Contains(field.SortExpression)) 
                {
                    string url = sortImage(field);
                    PlaceHolder panel = HeaderImages(field, url);
                    e.Row.Cells[i+1].Controls[0].Controls.Add(panel);
                }          
            }
        }


        private string sortImage(DataControlField field)
        {
            string url = string.Empty;
            string[] fullSortExp = SortExpression.Split(_sortingSeparator);
            List<string> fullSortExpression = new List<string>();
            fullSortExpression.AddRange(fullSortExp);

            //does the sort expression already exist?
            int index = fullSortExpression.FindIndex(s => s.Contains(field.SortExpression));
            if (index >= 0)
            {
                string s = fullSortExpression[index];
                if (s.Contains("ASC"))
                { url = "/_layouts/15/images/sortup.gif"; }
                else
                { url = "/_layouts/15/images/sortdown.gif"; }
            }
            return url;
        }


        private PlaceHolder HeaderImages(DataControlField field, string imageUrl)
        {
            Image filterIcon = new Image();
            filterIcon.ImageUrl = imageUrl;
            filterIcon.Style[HtmlTextWriterStyle.MarginLeft] = "2px";

            Literal headerText = new Literal();
            headerText.Text = field.HeaderText;

            PlaceHolder panel = new PlaceHolder();
            panel.Controls.Add(headerText);

            //add the sort icon if needed
            if (FilterExpression.Contains(field.SortExpression) &&
                SortExpression.Contains(field.SortExpression)) 
            {
                string url = sortImage(field);
                Image sortIcon = new Image();
                sortIcon.ImageUrl = url;
                sortIcon.Style[HtmlTextWriterStyle.MarginLeft] = "1px";
                panel.Controls.Add(sortIcon);
                //change the left margin to 1
                filterIcon.Style[HtmlTextWriterStyle.MarginLeft] = "1px";
            }

            panel.Controls.Add(filterIcon);
            return panel;
        }


        void GridView_Sorting(object sender, GridViewSortEventArgs e)
        {
            EnsureChildControls();
            string sDir = e.SortDirection.ToString();
            sDir = sDir == "Descending" ? " DESC" : "";

            SortExpression = e.SortExpression + sDir;
            e.SortExpression = SortExpression;

            //keep the filter
            if (!string.IsNullOrEmpty(FilterExpression))
            { 
                _gridDS.FilterExpression = FilterExpression; 
            }

            //cancel the sort - we handle the sorting within the select; Allowing it to run leads to errors
            e.Cancel = true;

        }


        void BuildFilterView(string filterExp)
        {
            string lastExp = filterExp;
            if (lastExp.Contains("AND"))
            {
                if (lastExp.Length < lastExp.LastIndexOf("AND") + 4)
                { lastExp = lastExp.Substring(lastExp.LastIndexOf("AND") + 4); }
                else
                { lastExp = string.Empty; }
            }
            
            //update the filter
            if (!string.IsNullOrEmpty(lastExp))
            { FilterExpression = lastExp; }

            //reset object dataset filter
            if (!string.IsNullOrEmpty(FilterExpression))
            {   
                _gridDS.FilterExpression = FilterExpression; 
            }
        }

        public UI.SPGridViewDataSource SetObjectDataSource(string dataSourceId, Data.TemplateViewType templateViewType, string strSearch, Utility.TemplateLibrarySettings settings, Guid viewId) 
        {
            var dataSource = new UI.SPGridViewDataSource();
            dataSource.ID = dataSourceId;
            dataSource.SelectMethod = "SelectData";
            dataSource.TypeName = dataSource.GetType().AssemblyQualifiedName;
            dataSource.EnableViewState = true;
            dataSource.FilterExpression = FilterExpression;
            dataSource.SelectParameters.Add("strTemplateViewType", TypeCode.String, templateViewType.ToString());
            dataSource.SelectParameters.Add("search", TypeCode.String, strSearch);
            dataSource.SelectParameters.Add("strSettings", TypeCode.String, settings.ToString());
            dataSource.SelectParameters.Add("sortExpression", TypeCode.String, SortExpression);
            dataSource.SelectParameters.Add("strViewId", TypeCode.String, viewId.ToString());
            this.GridDataSource = dataSource;
            return dataSource; 
        }

        
    }


 
}
